package com.three.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.FileWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.three.config.game.*;
import com.three.constant.ConfigConstants;
import com.three.exception.GameException;
import com.three.config.game.annotation.ConfigConstruct;
import com.three.config.game.annotation.ConfigMethodType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

/**
 * @Author mathua
 * @Date 2017/5/18 20:21
 */
public final class ConfigUtils {
    private static Log log = LogFactory.getLog(ConfigUtils.class);
    private static Map<Class<?>, Map<Long, Object>> CACHE = new HashMap<Class<?>, Map<Long, Object>>();
    private static String CLASS_PACKAGE = "";

    @SuppressWarnings("unchecked")
    public static <T> Map<Long, T> getConfigs(Class<T> clazz) {
        return (Map<Long, T>) CACHE.get(clazz);
    }

    public static void main(String[] args) throws Exception {
        loadAllLuaConfig("D:\\game_version\\chess\\sso\\src\\main\\resources\\config", "C:\\Users\\admin\\Desktop\\u3d-cfg");
    }

    /**
     *  加载指定目录下的所有配置文件，并生成lua配置文件
     * @param conifgDirPath 源目录
     * @param outDirPath 输出目录
     * @throws Exception
     */
    public static void loadAllLuaConfig(String conifgDirPath, String outDirPath) {
        try {
            File file = /*new File(conifgDirPath);*/FileUtils.readDirectoryByClasspath(conifgDirPath);
            if (!file.exists() || !file.isDirectory()) {
                throw new GameException("配置表文件路径[" + file.getAbsolutePath() + "]不存在!");
            }
            File outDir = /*new File(outDirPath);*/FileUtils.readDirectoryByClasspath(outDirPath);
            if (!outDir.exists()) {
                outDir.mkdir();
            }
            File[] files = file.listFiles(new FileFilter() {
                public boolean accept(File pathname) {
                    String tmpPathName = pathname.getName().toLowerCase();
                    return tmpPathName.endsWith(".xls") || tmpPathName.endsWith(".xlsx");
                }
            });
            for (File f : files) {
                try {
                    String fileName = f.getName().toLowerCase();
                    if (!fileName.startsWith("~$"))
                        parseLuaCfgFromFile(f, outDir);
                } catch (GameException e) {
                    if (log.isErrorEnabled())
                        log.error("文件:" + f.getName() + "," + e.getErrorMsg());
                }
            }
        } catch (GameException e) {
            System.out.println(e.getErrorMsg());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载指定目录下所有的配置文件
     *
     * @param dir
     * @param _packet
     * @throws Exception
     */
    public static void loadAllConfig(String dir, String _packet) {
        try {
            CLASS_PACKAGE = _packet;
            File file = FileUtils.readDirectoryByClasspath(dir);
            if (!file.exists() || !file.isDirectory()) {
                throw new GameException("配置表文件路径[" + file.getAbsolutePath() + "]不存在!");
            }
            File[] files = file.listFiles(new FileFilter() {
                public boolean accept(File pathname) {
                    String tmpPathName = pathname.getName().toLowerCase();
                    return tmpPathName.endsWith(".xls") || tmpPathName.endsWith(".xlsx");
                }
            });
            for (File f : files) {
                try {
                    String fileName = f.getName().toLowerCase();
                    if (!fileName.startsWith("~$"))
                        parseFromFile(f);
                } catch (GameException e) {
                    if (log.isErrorEnabled())
                        log.error("文件:" + f.getName() + "," + e.getErrorMsg());
                }
            }
            // 做配置表的初始化和检查
            initAndCheckConfigsAfterParse();
        } catch (GameException e) {
            System.out.println(e.getErrorMsg());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void initAndCheckConfigsAfterParse() {
        Set<Class<?>> set = CACHE.keySet();
        // 初始化配置表
        for (Class<?> clazz : set) {
            if (!executeMethod(clazz, ConfigMethodType.INIT)) {
                log.error("配置类:" + clazz.getName() + "初始化失败!");
                return;
            }
        }
        // 检查配置表
        for (Class<?> clazz : set) {
            if (!executeMethod(clazz, ConfigMethodType.CHECK)) {
                log.error("配置类:" + clazz.getName() + "配置配置出错!");
                return;
            }
        }
    }

    /**
     * 执行注解静态方法
     *
     * @param clazz 配置类
     * @param type  方法类型
     * @return
     */
    private static <T extends Annotation> boolean executeMethod(Class<?> clazz, ConfigMethodType type) {
        Method[] months = clazz.getDeclaredMethods();
        boolean flag = false;
        for (Method method : months) {
            method.setAccessible(true);
            ConfigConstruct construct = method.getAnnotation(ConfigConstruct.class);
            if (construct == null || construct.type() != type)
                continue;
            try {
                if (construct.isReturn()) {
                    boolean rs = (boolean) method.invoke(null, new Object[]{});
                    if (!rs) {
                        log.error("execute [" + method + "] error");
                        return false;
                    }
                } else {
                    method.invoke(null, new Object[]{});
                }
                flag = true;
            } catch (Exception e) {
//                log.error(e);
                e.printStackTrace();
                return false;
            }
        }
        if (!flag)
            log.debug("not found method [" + type + "]");
        return true;
    }

    private static void parseLuaCfgFromFile(File configFile, File outDir) throws Exception {
        Table table = readClientLuaExcel(configFile);
        StringBuffer luaCfg = table.parseToLua();
        luaCfg.toString();
        FileWriter fw = new FileWriter(outDir.getPath() + "\\" + table.getClientName() + ".lua");
        fw.write(luaCfg.toString());
        fw.close();
    }

    @SuppressWarnings("unchecked")
    private static void parseFromFile(File configFile) throws Exception {
        Table table = readServerExcel(configFile);
        String clazzName = CLASS_PACKAGE + "." + table.getClazzName();
        Class<?> clazz;
        try {
            clazz = Class.forName(clazzName);
        } catch (ClassNotFoundException e) {
            throw new GameException("类:" + clazzName + "不存在!");
        }

        Map<Long, Object> map = new HashMap<>();
        try {
            Iterator<List<Object>> iter = table.iterator();
            while (iter.hasNext()) {
                // 创建一个空的配置对象出来
                Object obj = clazz.newInstance();
                List<Object> data = iter.next();
                for (int i = 0; i < table.getHeads().size(); ++i) {
                    Head head = table.getHeads().get(i);
                    Object value = data.get(i);
                    if (head.getHeadBase().getType() != Type.LIST) {
                        Field field = clazz.getDeclaredField(head.getHeadBase().getName());
                        field.setAccessible(true);
                        field.set(obj, value);
                    } else {
                        Field field = clazz.getDeclaredField(head.getHeadBase().getName());
                        field.setAccessible(true);
                        switch (head.getListHead().getHeadType()) {
                            case BASE:
                                // 基础类型,obj其实是一个list
                                field.set(obj, (List<Object>) data.get(i));
                                break;
                            case NESTED:
                                List<Object> values = new ArrayList<>();
                                List<Object> list = (List<Object>) data.get(i);
                                for (Object tmpObj : list) {
                                    List<Object> subDataList = (List<Object>) tmpObj;
                                    String subClazzName = CLASS_PACKAGE + "." + head.getListHead().getClassName();
                                    Class<?> subClazz;
                                    try {
                                        subClazz = Class.forName(subClazzName);
                                    } catch (ClassNotFoundException e) {
                                        throw new GameException("类:" + subClazzName + "不存在!");
                                    }
                                    Object subObj = subClazz.newInstance();
                                    for (int j = 0; j < head.getListHead().getHeadBaseList().length; ++j) {
                                        Object subValue = subDataList.get(j);
                                        Field subField = subClazz
                                                .getDeclaredField(head.getListHead().getHeadBaseList()[j].getName());
                                        subField.setAccessible(true);
                                        subField.set(subObj, subValue);
                                    }
                                    values.add(subObj);
                                }
                                field.set(obj, values);
                                break;
                            default:
                                throw new GameException("列表类型错误:" + head.getListHead().getHeadType());
                        }
                    }
                }
                Number num = (Number) data.get(0);
                if (map.containsKey(num.longValue())) {
                    throw new GameException("文件:" + configFile.getName() + "出现重复id:" + num.longValue());
                }
                map.put(num.longValue(), obj);
            }
        } catch (GameException e) {
            throw new GameException("将文件配置反向控制注册到Java类中失败,原因：" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
        CACHE.put(clazz, map);
    }

    /**
     * 读取前端lua配置文件
     * @param configFile
     * @return
     * @throws Exception
     */
    private static Table readClientLuaExcel(File configFile) throws Exception  {
        Workbook book = WorkbookFactory.create(configFile);
        Sheet sheet = book.getSheetAt(0);
        Row row = sheet.getRow(ConfigConstants.CLASS_NAME_ROW);
        String clazzName = row.getCell(ConfigConstants.CLASS_NAME_CELL).getStringCellValue();
        if (clazzName == null || "".equals(clazzName.trim())) {
            throw new GameException(" 第" + (ConfigConstants.CLASS_NAME_ROW + 1) + "行，第"
                    + (ConfigConstants.CLASS_NAME_CELL + 1) + "列,服务器输出文件名有误.");
        }
        row = sheet.getRow(ConfigConstants.CLIENT_NAME_ROW);
        String clientName = row.getCell(ConfigConstants.CLIENT_NAME_CELL).getStringCellValue();
        if (clientName == null || "".equals(clientName.trim())) {
            throw new GameException(" 第" + (ConfigConstants.CLIENT_NAME_ROW + 1) + "行，第"
                    + (ConfigConstants.CLIENT_NAME_CELL + 1) + "列,客户端输出文件名有误.");
        }
        // 标志行(标志【前段/后端/双端】配置)
        Row flagRow = sheet.getRow(ConfigConstants.FLAG_ROW);
        // 名字行
        Row nameRow = sheet.getRow(ConfigConstants.NAME_ROW);
        // 类型行(8大基本类型+list类型)
        Row typeRow = sheet.getRow(ConfigConstants.TYPE_ROW);
        List<Head> heads = praseHead(flagRow, nameRow, typeRow);

        int cellStart = nameRow.getFirstCellNum();
        int cellEnd = nameRow.getLastCellNum();
        List<List<Object>> datas = new ArrayList<>();

        for (int i = ConfigConstants.TYPE_ROW + 1; i <= sheet.getLastRowNum(); ++i) {
            Row dataRow = sheet.getRow(i);
            if (dataRow == null || dataRow.getCell(0) == null) {
                // throw new GameException("第" + (i + 1) + "行没有数据.");
                break;
            }
            datas.add(parseServerData(heads, cellStart, cellEnd, dataRow));
        }

        Table table = Table.build(clazzName, clientName, heads, datas);
        table.trimClient();
        return table;
    }

    /**
     * 读取后端配置文件
     *
     * @param configFile
     * @return
     * @throws Exception
     */
    private static Table readServerExcel(File configFile) throws Exception {
        Workbook book = WorkbookFactory.create(configFile);
        Sheet sheet = book.getSheetAt(0);
        Row row = sheet.getRow(ConfigConstants.CLASS_NAME_ROW);
        String clazzName = row.getCell(ConfigConstants.CLASS_NAME_CELL).getStringCellValue();
        if (clazzName == null || "".equals(clazzName.trim())) {
            throw new GameException(" 第" + (ConfigConstants.CLASS_NAME_ROW + 1) + "行，第"
                    + (ConfigConstants.CLASS_NAME_CELL + 1) + "列,服务器输出文件名有误.");
        }
        row = sheet.getRow(ConfigConstants.CLIENT_NAME_ROW);
        String clientName = row.getCell(ConfigConstants.CLIENT_NAME_CELL).getStringCellValue();
        if (clientName == null || "".equals(clientName.trim())) {
            throw new GameException(" 第" + (ConfigConstants.CLIENT_NAME_ROW + 1) + "行，第"
                    + (ConfigConstants.CLIENT_NAME_CELL + 1) + "列,客户端输出文件名有误.");
        }
        // 标志行(标志【前段/后端/双端】配置)
        Row flagRow = sheet.getRow(ConfigConstants.FLAG_ROW);
        // 名字行
        Row nameRow = sheet.getRow(ConfigConstants.NAME_ROW);
        // 类型行(8大基本类型+list类型)
        Row typeRow = sheet.getRow(ConfigConstants.TYPE_ROW);
        List<Head> heads = praseHead(flagRow, nameRow, typeRow);

        int cellStart = nameRow.getFirstCellNum();
        int cellEnd = nameRow.getLastCellNum();
        List<List<Object>> datas = new ArrayList<>();

        for (int i = ConfigConstants.TYPE_ROW + 1; i <= sheet.getLastRowNum(); ++i) {
            Row dataRow = sheet.getRow(i);
            if (dataRow == null || dataRow.getCell(0) == null) {
                // throw new GameException("第" + (i + 1) + "行没有数据.");
                break;
            }
            datas.add(parseServerData(heads, cellStart, cellEnd, dataRow));
        }

        Table table = Table.build(clazzName, clientName, heads, datas);
        table.trimServer();
        return table;
    }

    /**
     * 转化出服务器的配置数据
     *
     * @param heads     配置头信息
     * @param cellStart 一行单元格的开始索引
     * @param cellEnd   一行单元格的结束索引
     * @param dataRow   行数据对象
     * @return
     * @throws GameException
     */
    private static List<Object> parseServerData(List<Head> heads, int cellStart, int cellEnd, Row dataRow)
            throws GameException {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < cellEnd - cellStart; ++i) {
            Head head = heads.get(i);
            if (!head.isServer())
                continue;
            try {
                Cell dataCell = dataRow.getCell(cellStart + i);
                Object value = getCellValue(dataCell, head);
                list.add(value);
            } catch (GameException e) {
                throw new GameException(
                        "第" + (dataRow.getRowNum() + 1) + "行，第" + (cellStart + i + 1) + "列," + e.getErrorMsg());
            }
        }
        return list;
    }

    private static Object getCellValue(Cell cell, Head head) throws GameException {
        if (cell == null) {
            throw new GameException("数据为空.");
        }

        Type type = head.getHeadBase().getType();
        Object tmpValue = null;
        switch (cell.getCellType()) {
            case Cell.CELL_TYPE_NUMERIC:
                tmpValue = cell.getNumericCellValue();
                break;
            case Cell.CELL_TYPE_STRING:
                tmpValue = cell.getStringCellValue();
                break;
            case Cell.CELL_TYPE_FORMULA:
                // 公式
                tmpValue = String.valueOf(cell.getCellFormula());
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                tmpValue = cell.getBooleanCellValue();
                break;
            case Cell.CELL_TYPE_BLANK:
            case Cell.CELL_TYPE_ERROR:
            default:
                throw new GameException("数据出错.");
        }
        try {
            switch (type) {
                case LONG:
                    if (tmpValue instanceof String) {
                        return Long.parseLong((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).longValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case INT:
                    if (tmpValue instanceof String) {
                        return Integer.parseInt((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).intValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case SHORT:
                    if (tmpValue instanceof String) {
                        return Short.parseShort((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).shortValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case BYTE:
                    if (tmpValue instanceof String) {
                        return Byte.parseByte((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).byteValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case BOOLEAN:
                    if (tmpValue instanceof String) {
                        return Boolean.parseBoolean((String) tmpValue);
                    } else if (tmpValue instanceof Boolean) {
                        return tmpValue;
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case FLOAT:
                    if (tmpValue instanceof String) {
                        return Float.parseFloat((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).floatValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case DOUBLE:
                    if (tmpValue instanceof String) {
                        return Double.parseDouble((String) tmpValue);
                    } else if (tmpValue instanceof Double) {
                        return ((Number) tmpValue).doubleValue();
                    } else {
                        throw new GameException("错误数据：" + tmpValue);
                    }
                case STRING:
                    return String.valueOf(tmpValue);
                case LIST:
                    String splitStr = head.getListHead().getSplitStr();
                    String value = StringUtils.replaceChinesePunctuation(String.valueOf(tmpValue));
                    String[] itemList = value.trim().split("\\" + splitStr);
                    List<Object> list = new ArrayList<Object>();
                    switch (head.getListHead().getHeadType()) {
                        case BASE:// 基本类型
                            if (head.getListHead().getType() == Type.LIST) {
                                throw new GameException("类型错误：" + tmpValue);
                            }
                            for (String item : itemList) {
                                list.add(praseBaseTypeValue(item, head.getListHead().getType()));
                            }
                            return list;
                        case NESTED:// 嵌套类型
                            for (String item : itemList) {
                                String subItemList[] = item.split(",");
                                if (subItemList.length != head.getListHead().getHeadBaseList().length) {
                                    throw new GameException("数据错误，数据：" + tmpValue + " 与类型定义对不上.");
                                }
                                List<Object> subList = new ArrayList<>();
                                for (int i = 0; i < subItemList.length; ++i) {
                                    subList.add(praseBaseTypeValue(subItemList[i],
                                            head.getListHead().getHeadBaseList()[i].getType()));
                                }
                                list.add(subList);
                            }
                            return list;
                        default:
                            throw new GameException("类型错误：" + tmpValue);
                    }
                default:
                    throw new GameException("错误数据：" + tmpValue);
            }
        } catch (GameException e) {
            throw new GameException(e.getErrorMsg());
        } catch (Exception e) {
            throw new GameException("错误数据：" + tmpValue);
        }
    }

    private static Object praseBaseTypeValue(String value, Type type) throws GameException {
        try {
            switch (type) {
                case LONG:
                    return Long.parseLong(value);
                case INT:
                    return Integer.parseInt(value);
                case SHORT:
                    return Short.parseShort(value);
                case BYTE:
                    return Byte.parseByte(value);
                case BOOLEAN:
                    return Boolean.parseBoolean(value);
                case FLOAT:
                    return Float.parseFloat(value);
                case DOUBLE:
                    return Double.parseDouble(value);
                case STRING:
                    try {
                        Number num = NumberFormat.getInstance().parse(value);
                        return String.valueOf(num.longValue());
                    }catch(Exception e) {
                        return value;
                    }
                default:
                    throw new GameException("错误数据：" + value);
            }
        } catch (Exception e) {
            throw new GameException("错误数据：" + value);
        }
    }

    /**
     * 读取配置头部信息
     *
     * @param flagRow 标志行
     * @param nameRow 名字行
     * @param typeRow 类型行
     * @return
     * @throws GameException
     */
    private static List<Head> praseHead(Row flagRow, Row nameRow, Row typeRow) throws GameException {
        int cellStart = nameRow.getFirstCellNum();
        int cellEnd = nameRow.getLastCellNum();
        List<Head> heads = new ArrayList<Head>();
        for (int i = cellStart; i < cellEnd; ++i) {
            String name = nameRow.getCell(i).getStringCellValue();
            if (name == null || "".equals(name.trim())) {
                throw new GameException(" 第" + (nameRow.getRowNum() + 1) + "行，第" + (i + 1) + "列,配置表字段名字有错.");
            }
            String type = typeRow.getCell(i).getStringCellValue();
            if (type == null || "".equals(name.trim())) {
                throw new GameException(" 第" + (typeRow.getRowNum() + 1) + "行，第" + (i + 1) + "列,配置表字段类型有错.");
            }
            int flag = 0;
            try {
                flag = (int) flagRow.getCell(i).getNumericCellValue();
            } catch (Exception e) {
                throw new GameException(" 第" + (flagRow.getRowNum() + 1) + "行，第" + (i + 1) + "列,配置表字段标志有错.");
            }
            boolean isClient = false;
            boolean isServer = false;
            if ((flag & 0x1) == 0x1)
                isClient = true;
            if ((flag & 0x2) == 0x2)
                isServer = true;
            HeadBase headBase = null;
            try {
                headBase = HeadBase.build(name, toType(type));
            } catch (GameException e) {
                throw new GameException(" 第" + (typeRow.getRowNum() + 1) + "行，第" + (i + 1) + "列," + e.getErrorMsg());
            }
            ListHead listHead = null;
            if (headBase != null && headBase.getType() == Type.LIST) {
                // 替换字符串
                type = StringUtils.replaceChinesePunctuation(type.trim());
                try {
                    listHead = toListHead(type);
                } catch (GameException e) {
                    throw new GameException(
                            " 第" + (typeRow.getRowNum() + 1) + "行，第" + (i + 1) + "列," + e.getErrorMsg());
                }
            }
            heads.add(Head.build(headBase, listHead, isServer, isClient));
        }
        return heads;
    }

    private static ListHead toListHead(String type) throws GameException {
        int startIndex = type.indexOf("<");
        int endIndex = type.lastIndexOf(">");
        type = type.substring(startIndex + 1, endIndex);
        if (type == null || "".equals(type)) {
            throw new GameException("类型错误：" + type);
        }

        String splitStr = type.substring(type.length() - 1);
        if (splitStr == null || "".equals(splitStr)) {
            throw new GameException("类型错误：" + type);
        }

        type = type.substring(0, type.length() - 1);
        if (type.contains("(") && type.contains(")")) {
            String className = type.substring(0, type.indexOf("("));
            if (className == null || "".equals(className)) {
                throw new GameException("类型错误：" + type);
            }
            String listHeadStr = type.substring(type.indexOf("(") + 1, type.lastIndexOf(")"));
            if (listHeadStr == null || "".equals(listHeadStr)) {
                throw new GameException("类型错误：" + type);
            }
            String listHeads[] = listHeadStr.split(",");
            HeadBase headBaseList[] = new HeadBase[listHeads.length];
            for (int i = 0; i < listHeads.length; ++i) {
                String heads[] = listHeads[i].split(":");
                if (heads.length != 2) {
                    throw new GameException("类型错误：" + type);
                }
                headBaseList[i] = HeadBase.build(heads[0], toType(heads[1]));
            }
            return ListHead.build(className, headBaseList, splitStr);
        } else {
            return ListHead.build(toType(type), splitStr);
        }

    }

    private static Type toType(String type) throws GameException {
        type = type.trim().toLowerCase();
        switch (type) {
            case "long":
                return Type.LONG;
            case "int":
                return Type.INT;
            case "short":
                return Type.SHORT;
            case "byte":
                return Type.BYTE;
            case "float":
                return Type.FLOAT;
            case "double":
                return Type.DOUBLE;
            case "boolean":
                return Type.BOOLEAN;
            case "string":
                return Type.STRING;
            default:
                if (type.startsWith("list"))
                    return Type.LIST;
                throw new GameException("不存在该类型：" + type);
        }
    }
}
